using System;
using System.Html;
using System.Runtime.CompilerServices;
using Awaiter;
using jQueryApi;

namespace Nsb.Classes
{
    //[IgnoreNamespace]
    public partial class Evo
    {
        //
        // Currently responses must be synchronus.
        // Future version could allow asynchronus responses.
        //
        public string Name;
        public ResponseType Result;
        public object Value;
        public jQueryEvent Event;
        private Await _awp;
        private bool _delayResponse;
        private object _evoke;
        private string _evokeName; // Shared between _evoke and _fn
        private object _fn;
        private object _response;
        private string _responseName;
        private bool _ckFailed;

        #region Chains

        [AlternateSignature]
        public extern Evo SetValue(object value);

        public Evo SetValue(string name, object value)
        {
            Name = Arguments.Length > 1 ? name : "";
            Value = Arguments.Length > 1 ? value : name ?? null; // Also converts undefined to null
            return this;
        }

        [AlternateSignature]
        public extern Evo Evoke(Action<Evo> fn);

        public Evo Evoke(object ob, string name)
        {
            _evoke = ob;
            _evokeName = name ?? "";
            return this;
        }

        [AlternateSignature]
        public extern Evo Response(Action<Evo> fn);

        public Evo Response(object ob, string name)
        {
            _response = ob;
            _responseName = name ?? "";
            return this;
        }

        [AlternateSignature]
        public extern Evo Fn(object fn);

        public Evo Fn(object fn, string name)
        {
            _fn = fn;
            _evokeName = name ?? "";
            return this;
        }

        public Evo Ev(jQueryEvent e)
        {
            Event = e;
            return this;
        }

        [AlternateSignature]
        public extern Evo Ck(object o);

        /// <summary>
        /// Prevents fireing if the check clause is false.
        /// Only runs the check on creaton so does not re check on cached Evo's
        /// </summary>
        /// <param name="fn">bool check function</param>
        /// <returns></returns>
        public Evo Ck(BoolAction fn)
        {
            if (Type.GetScriptType(fn) != "function")
                _ckFailed = fn == null;
            else
            {
                _ckFailed = fn();
            }
            return this;
        }

        #endregion Chains

        #region Methods

        [AlternateSignature]
        public extern void FireWith(Action<Evo> fn);

        [AlternateSignature]
        public extern void FireWith(Action<Evo> fn, object value);

        public void FireWith(Action<Evo> fn, string name, object value)
        {
            Evoke(fn);
            if (Arguments.Length > 2)
            {
                Name = name;
                SetValue(value);
            }
            else if (Arguments.Length > 1)
                SetValue(name);
            Fire();
        }

        [AlternateSignature]
        public extern void FireOn(object ob, string method, object value);

        public void FireOn(object ob, string method, string name, object value)
        {
            Evoke(ob, method);
            if (Arguments.Length > 3)
                SetValue(name, value);
            else
                SetValue(name);
            Fire();
        }

        [AlternateSignature]
        public extern void FireAsync();

        public void FireAsync(Await awp)
        {
            _awp = awp ?? null; // Yes, never undefined, always at least null
            Result = ResponseType.Evoke;
            Window.SetTimeout(delegate { Fire(); }, 0);
        }

        [AlternateSignature]
        public extern void Fire();

        public void Fire(Await awp)
        {
            if (_ckFailed)
                return;
            _awp = awp ?? null; // Yes, never undefined, always at least null
            Result = ResponseType.Evoke;
            if (_evoke == null && _fn == null)
            {
                Respond();
                return;
            }
            if (String.IsNullOrEmpty(_evokeName))
            {
                if (_evoke != null)
                {
                    Action<Evo> fn = (Action<Evo>) _evoke;
                    fn(this);
                }
                else if (_fn != null)
                {
                    if (awp != null)
                    {
                        _awp = null; // Show it as used. Fn must perform Done() on it.
                        Action<Await, object> fn = (Action<Await, object>) _fn;
                        fn(awp, Value);
                    }
                    else
                    {
                        Action<object> fn = (Action<object>) _fn;
                        fn(Value);
                    }
                }
            }
            else
            {
                if (_evoke != null && Type.HasMethod(_evoke, _evokeName))
                {
                    Script.Literal("{0}[{1}]({2})", _evoke, _evokeName, this);
                }
                else if (_fn != null && Type.HasMethod(_fn, _evokeName))
                {
                    Script.Literal("{0}[{1}]({2})", _fn, _evokeName, Value);
                }
                else
                {
                    Result = ResponseType.NotFound;
                }
            }
            TryRespond();
        }

        private void TryRespond()
        {
            if (_delayResponse)
                return;
            Result = Result == ResponseType.Evoke ? ResponseType.Respond : Result;
            if (_response == null)
            {
                Destroy();
                return;
            }

            if (String.IsNullOrEmpty(_responseName))
            {
                Action<Evo> fn = (Action<Evo>) _response;
                fn(this);
            }
            else
            {
                if (Type.HasMethod(_response, _responseName))
                {
                    Script.Literal("{0}[{1}]({2})", _response, _responseName, this);
                }
            }
            Done();
        }

        public void Respond()
        {
            _delayResponse = false;
            TryRespond();
            Destroy();
        }

        public Await TakeAndOwnResponse()
        {
            Await awp = _awp;
            _awp = null;
            _delayResponse = true;
            return awp;
        }

        public void Done()
        {
            if (_awp != null)
            {
                _awp.Done();
                _awp = null;
            }
        }

        public void Destroy()
        {
            Done();
            _evoke = _response = Value = null;
            Result = ResponseType.Empty;
        }

        #endregion Methods

        #region Statics

        #endregion Statics
    }

    public enum ResponseType
    {
        Empty = 0,
        Evoke = 1,
        Respond = 2,
        NotFound = 3,
        Failed = 4
    }
}